框架1:Java Web

您所在的位置:网站首页 vue框架简介 简书 框架1:Java Web

框架1:Java Web

2023-04-01 23:53| 来源: 网络整理| 查看: 265

Java网页应用开发。它是【基于请求和响应】来开发的。 JavaSE中的结构是C/S Client-Server。 而JavaEE中Web是B/S Browser-Server。(浏览器不同,会有兼容问题,例如很多标签IE不兼容) ● 请求:客户端(浏览器)给服务器发送数据,叫请求Request; ● 响应:服务器给客户端(浏览器)回传数据,叫响应Response

1、JavaEE三层架构 JavaEE三层架构

项目中的包结构(分成多个包为了解耦,即降低代码的耦合度,方便项目后期的升级和维护): ● web/servlet/controller 接受请求和响应的web包 ● service Service接口包  ● service.impl Service接口实现类包 ● dao Dao接口包  ● dao.impl Dao接口实现类包 ● pojo/entity/domain/bean JavaBean类包 ● test 测试包 ● utils 工具包

2、web资源的分类

web资源按照实现的技术和呈现的效果,可以分为静态资源和动态资源。 ● 静态资源:html、css、js、txt文本、jpg图片、mp4视频... ● 动态资源:【Java中的】:jsp页面、Servlet程序

3、⭕ 创建Java Web项目:

(1) 新建一个项目(Web)。可以是静态Web等...

eclipse示例 (2) 创建并书写相应的文件。 (3) 浏览器运行。

4、web中的【/】意义

在web中,【/】是一种绝对路径。 ● 浏览器解析时,得到的地址是:http://ip:port/ ● 服务器解析是,得到的地址是:http://ip:port/工程名/  但注意,服务器中使用response.sendRedirect("/");时,会将该内容发送给浏览器重定向,服务器不解析,而是浏览器解析,得到http://ip:port/。

● XML ● JSON ● Tomcat ● Servlet ● Filter ● Listener ● JSP ● EL ● JSTL ● Cookie ● Session ● AJAX

一【HTML和CSS】、二【JavaScript】、三【JQuery】

参考:框架1:Java Web - 前端 - 简书 (jianshu.com)

四、XML

XML全称为Extensible Markup Language,可扩展的标记性语言。

● 主要作用: (1) 保存数据,而且这些数据具有自我描述性;

e.g Java中有Student对象: [ Student[id=1, name="张三"], Student[id=2, name="李四"] ] students.xml 1 张三 2 李四

⭕ 注意:XML文件必须要有且仅有一个根元素,即没有父标签的元素。 (2) 作为配置文件 (3) 也可以作为网络传输数据的格式。(现在主要使用json文件)

1.1 文档声明 以上内容就是xml的声明,其中: ● version 表示xml的版本 ● encoding 表示xml文件的编码方式 1.2 元素(标签)

从(且包括)开始标签直到(且包括)结束标签的部分。 元素可包含其他元素、文本或者两者的混合物。元素也可以拥有属性。 和html的元素(标签)定义一样,与之不同在于xml元素可以自命名。

● xml命名规则

xml命名规则

● xml元素属性 属性可以提供元素的额外信息。 和html一样,每个属性的值必须使用引号引起来。

● xml注释

< !-- 注释内容 -- > 【去掉空格箭号左右空格】

● 文本区域(CDATA区)

当要显示特殊字符时(例如【 1.3 xml解析技术

不管是html文件,还是xml文件,他们都是标记型文档,都可以使用w3c组织制定的dom技术来解析。

dom技术会将整个文档视作一个document对象。

(1) 历史背景 早期JDK为我们提供了两种xml解析技术DOM和SAX【已经过时,但我们需要了解一下】 dom解析技术最初是由W3C组织制定的,而所有的编程语言都结合自身特点对该项技术进行实现,java也不例外。 ● sun公司在JDK1.5时对dom解析技术进行了升级:SAX(Simple API for XML) SAX解析,和W3C制定的解析不太一样。它是以类似事件机制通过回调告诉用户当前正在解析的内容。也就是说,它不是一次性创建大量dom对象,而是用到时一行一行的解析。因此在内存和性能使用上,都优于DOM解析。 ● 第三方解析:  jdom 在dom基础上进行了封装;  dom4j 在jdom的基础上进行了封装;  pull 主要用在安卓手机开发,与sax非常类似,都是事件机制解析xml文件。

(2) dom4j解析技术 (\color{red}{重点}) 很多框架基于dom4j来解析xml文件。可以去dom4j官网下载jar包。

● dom4j 解析步骤

I. 项目中创建一个lib目录,并添加dom4j的jar包,然后添加到项目类库中。

添加到项目类库

II. 创建SAXReader输入流,读取xml配置文件,生成Document对象。

public class test{ public static void main(String args[]) throws Exception{ SAXReader saxReader = new SAXReader(); Document document = saxReader.read("xml文件路径"); } }

III. 通过document对象得到根元素,通过根元素得到对象元素(标签)。

Element rootElement = document.getRootElement();

IV. 遍历将对象元素(标签)进行处理,创建成我们想要的Java对象。

IV(1) 传入标签名,返回标签元素的集合 List elements= rootElement.elements("标签名"); for(Element e: elements){ IV(2) 传入标签名,返回想要的标签元素 Element subElement = e.element("标签名"); IV(3) 通过Element对象的getText()方法,得到标签内容 String content = subElement.getText(); IV(4) 也可以直接通过Element对象的elementText("标签名")方法,得到标签内容 String content2 = subElement.elementText("标签名"); IV(5) 通过Element对象的attributeValue()方法,得到属性值 String attributeValue = subElement.attributeValue("属性名"); ...根据得到的数据,创建Java对象... } 五、Tomcat

由Apache组织提供的一款Web服务器,提供对jsp和Servlet的支持。它是一种轻量级的javaWeb容器(服务器),也是当前应用最广的JavaWeb服务器(免费)。

● 补充:tomcat的端口号默认8080;而http协议的默认端口号是80,省略不显示【看不到端口号的address就是80端口】。

1、安装和启动

直接找到需要用的Tomcat版本对应的zip(windows)/tar.gz(Linux)压缩包,解压到需要安装的目录即可。

● 文件目录 | - Tomcat   | - bin 可执行文件   | - conf 配置文件    可以通过【server.xml】文件,修改默认的8080端口号,然后重启tomcat服务器,才生效。   | - lib jar包   | - logs 运行时输出的日志信息   | - temp 运行时产生的临时数据   | - webapps 部署的Web工程。一个目录对应一个工程!   | - work 工作目录,用来存放运行时jsp翻译为Servlet的源码,以及Session钝化(对象序列化)的目录

Session的"钝化"和"活化" 当用户在一段时间内没有与Web应用程序进行交互时,服务器可能会将该session视为“不活动”,并将其保存在内存或磁盘中,这就是所谓的"Session钝化"。这样可以释放内存资源,避免空闲的session占用太多服务器资源。但是,一旦用户再次与应用程序进行交互,服务器就会重新“激活”这个session,重新加载之前保存的用户数据,并让用户可以继续他们之前的操作,这就是所谓的"Session活化"。

● 启动和关闭tomcat服务器

【终端输入】 1、启动tomcat服务器 >sh ./startup.sh 【也可以去指定sh文件双击】 2、关闭tomcat服务器 >sh ./shutdown.sh 【也可以去指定sh文件双击】 (windows下是双击bat批处理文件,并且要保持小黑框一直打开)

⭕ 测试是否成功启动tomcat: 在浏览器中输入任意一项地址: (1) http://localhost:8080 (2) http://127.0.0.1:8080 (3) http://真实ip地址:8080

MacOS安装、启动和关闭 参考链接:Mac-Tomcat安装教程小白教学mac 安装tomcat爱吃Java的猴子的博客-CSDN博客

2、部署web工程到tomcat

部署后可以实现网络访问。

● 部署前工作【程序员的工作】:告诉tomcat,要部署哪个工程  ● 方式一:将web工程拷贝到tomcat的webapps目录下。  ● 方式二:配置文件映射。(这种方式不要求项目必须存在于webapps下)    在【tomcat/conf/Catalina/localhost】目录下新建xml文件。【一般一个工程一个文件】    ● Context表示一个工程上下文      ● 属性path表示工程在浏览器中的访问路径,属性docBase表示你的工程目录实际存放的位置。

mytest.xml

 ● 访问webapps的工程:http://ip:port/工程名/目录/html文件   ● http://localhost:8080 实质是搜索tomcat的webapps目录   ● http://localhost:8080(没有工程名)实质是访问到tomcat的webapps目录下的Root工程   ● http://localhost:8080/工程名(没有资源名)实质是访问到对应工程的index.html

Tomcat中的web.xml中的欢迎页面设置

● 部署时工作【IDE工作】:IDE拷贝了一份Tomcat【副本】,在【副本】中部署了【编译后的工程路径】(含有原工程的所有资源文件+class文件),映射方式是上面提到的方式二。  理解:相当于有一份tomcat备份,一份工程备份(不含java源文件,仅含编译后的class文件)。拷贝一份tomcat,是为了不影响源tomcat。程序员做了一次映射,IDE又做了一次映射,项目运行时,实际跑的是IDE的映射环境。\color{blue}{底层理解}

⭕ 将页面文件拖入浏览器 vs 访问部署到tomcat的页面文件 区别 (1) 将文件拖入浏览器:走的是【file】协议,直接访问本地文件管理器,拿到文件后浏览器进行解析,不走网络。 (2) 访问部署到tomcat的页面文件:走的协议是【http】,通过网络向服务器发送请求,tomcat响应回传页面文件,客户端拿到后再使用浏览器进行解析显示。

3、tomcat部署到IDE

(1) IntelliJ IDEA IntelliJ IDEA > Settings... > Build, Execution, Deployment > Application Servers (2) Eclipse Window > Preferences > Server > Runtime Environments

4、动态的web工程

与上面的创建web工程、部署tomcat到IDE两步走不同, 创建动态web工程,会一次性将服务器等准备好,并且项目目录也会不一样。

(1) 目录介绍 动态WEB工程目录的介绍

有时候,WEB-INF里面是没有lib目录的,我们习惯上将其创建出来,存放第三方的jar包。

(2) 动态web工程 vs 静态web工程

I. 静态 WEB 静态 WEB指的以*.htm、*.html 为后缀的网页,这些网页的访问只是从服务器上读取这些内容,然后返回给客户端浏览器解析呈现在用户面前。静态WEB【缺点】在于:所有用户看到的效果一样,无法实现与用户动态交互:不能登录连接数据库。 此外,静态资源(如html文件)可以直接手动拖入浏览器,进行显示。

II. 动态WEB 动态 WEB是指利用某些技术实现连接数据库,能够与用户交互,使 WEB的展示效果“因时因人而变”。它的好处是能够连接数据库,实现与用户的交互。强调一点,不是网站中有动态的效果就是动态WEB,动态WEB是指的是客户端与用户能够进行交互。 此外,动态资源(如jsp文件)无法拖入浏览器直接显示,它是需要服务器动态拼接内容。 主要是将请求先转交给WEB Container(WEB服务器的容器),在WEB Container中连接数据库,从数据库中取出数据等一系列操作后动态拼凑页面的展示内容,拼凑页面的展示内容后,把所有的展示内容交还给WEB服务器,之后通过WEB服务器将内容发送回客户端浏览器进行解析执行。

(3) 热部署

一般服务器(比如tomcat,jboss等)启动以后,我们还需要进一步修改java代码,或者是jsp代码。一般来说,改完重启以后才会生效。但如果配置了服务器的热部署,就可以改完代码后立即生效,而不是重启服务器再生效。这样就会节省大量时间!

● 如何热部署 一般有两个选项:

I. On Update action:当代码改变的时候,需要IDEA为你做什么  -Update resources:如果发现有更新,而且更新的是资源文件(*.jsp,*.xml等,不包括java文件),就会立刻生效  -Update classes and resources【推荐】:如果发现有更新,这个是同时包含java文件和资源文件的,就会立刻生效 ⭕ 注意:在运行模式下,修改java文件时不会立刻生效的;而debug模式下,修改java文件时可以立刻生效的。当然,两种运行模式下,修改resources资源文件都是可以立刻生效的。  -Redploy:重新部署,只是把原来的war删掉(项目重新部署),不重启服务器  -Restart:重启服务器

II. On Frame deactivation:当失去焦点(比如最小化了IDEA窗口),需要IDEA为你做什么  -Do nothing【推荐】: 不做任何事 (一般推荐这个,因为失去焦点的几率太大)  -Update resources: 失去焦点后,修改的resources文件都会立刻生效  -Update classes and resources:失去焦点后,修改的java ,resources文件都会立刻生效(与On update action中的Update classes and resources一样,也是运行模式修改的java文件不会生效,debug模式修改的java文件会立刻生效)

参考链接:IDEA 服务器热部署详解(On Update action/On frame deactivation)_王溺码的博客-CSDN博客

5、使用tomcat统一的错误页面配置

在web.xml配置标签,出错后,会自动跳转对应页面。

web.xml ... 错误响应码(500、404...) 出错后,对应跳转的错误页面路径

⭕ 一定要将异常一直抛到最外层,抛给Tomcat容器,如果在内部捕获后不抛出,Tomcat无法感知。

六、Servlet

Servlet是JavaEE规范之一,即接口。

它是运行在服务器上的一个Java小程序(Server applet),功能:用来接收客户端发送的请求,并响应数据给客户端。 Servlet是JavaWeb三大组件【Servlet程序、Filter过滤器、Listener监听器】之一。

Tomcat服务器和Servlet版本的对应关系 1、编写类实现Servlet接口+接入动态WEB 流程

(0) 前期工作:动态web项目的 web/WEB-INF 目录下 (web/WEB-INF/lib) 导入额外的jar包 ——【serverlet-api.jar】(这个包在tomcat的lib目录下)。 (1) 编写Java类,实现Servlet接口

public class myServlet implements Servlet{ @Override public void init(ServletConfig servletConfig) throws ServletException{ super.init(config); //必须保留,不然config没有传递进去。【GenericServlet类才持有ServletConfig对象】 } @Override public ServletConfig getServletConfig(){ } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException{ 该方法非常重要!它是专门用来处理请求和响应的! } @Override public String getServletInfo(){ return null; } @Override public void destroy(){ } }

(2) 编写web.xml文件,将编写好的Servlet实现类映射到浏览器访问路径下

自定义servlet别名(一般和实现类名保持一致) servlet实现类的全类名(含包名) 需要映射的servlet名字(和上面保持一致) 资源访问路径(例如/hello)

成对配置,一个是配置servlet本身,另一个是配置servlet和浏览器的访问路径的映射。 ● 中的资源访问路径,必须要以【/】打头,它表示项目的根目录。 ● 此后,当浏览器访问 http://ip:port/工程路径/资源访问路径 时,会默认调用实现类中的service方法。

2、Servlet的生命周期

2.1 执行servlet的构造器   (单例,仅执行一次) 2.2 执行init初始化方法   (仅执行一次) 2.3 执行service方法   (每次刷新对应的访问路径,都会被调用) 2.4 执行destroy销毁方法   (web工程停止时调用,仅执行一次)

3、GET方法和POST方法的分发

(1) 自定义Servlet类,实现Servlet接口。 一般客户端向服务器发送get请求和post请求,所要实现的功能不一样。 而servlet中只有service方法,因此需要进行分发处理。

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException{ 3.1 向下转型为HttpServletRequest,使用其中的getMethod方法 HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest; 3.2 通过method进行分发 String method = httpServletRequest.getMethod(); if("GET".equals(method)){ ... }else if("POST".equals(method)){ ... } }

(2) 自定义Servlet类,继承HttpServlet类。【常用】 根据业务需要,重写HttpServlet的doGet()和doPost()方法。

public class myServlet2 extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ ... } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ ... } } 4、Servlet的继承体系 Servlet继承体系 5、ServletConfig

Servlet程序的配置信息接口。一个Servlet内含有对应的一个ServletConfig实现类对象。 \color{red}{注意:Servlet程序和ServletConfig都是由Tomcat负责创建,我们仅负责使用。}

● 作用: (1) 获取servlet在web.xml中配置的name。 (2) 获取servlet在web.xml中配置的init-param(键值对),通过键取值。 (3) 获取servlet的上下文对象ServletContext。

public class HelloServlet implements Servlet{ @Override public void init(ServletConfig servletConfig)throws ServletException{ super.init(config); //必须保留,不然config没有传递进去。【GenericServlet类才持有ServletConfig对象】 System.out.println("HelloServlet程序的别名:" + servletConfig.getServletName()); System.out.println("HelloServlet程序的初始化参数username值是:" + servletConfig.getInitParameter("username")); System.out.println("HelloServlet程序的初始化参数url值是:" + servletConfig.getInitParameter("url")); System.out.println("HelloServlet程序的上下文对象是:" + servletConfig.getServletContext()); } } web.xml HelloServlet HelloServlet全类名 username root url jdbc:mysql://localhost:3306/test HelloServlet /hello 6、ServletContext

接口,它表示Servlet上下文对象。 \color{red}{注意:【一个web工程,只有一个ServletContext实例】(不管有多少个Servlet)。在web工程部署时创建,在web工程停止时销毁。}

● ServletContext是一个域对象。 域对象,是可以像Map一样存取数据的对象。这里的域,是指存取数据的操作范围,即整个web工程。

Map vs 域对象

● 如何获得ServletContext对象 (1) 可以通过Servlet内的ServletConfig对象得到。 (2) 调用servlet的getServletContext()得到。【本质:GenericServlet中封装的方法,内部还是用方法(1)!!!】。

在Servlet实现类中: 方法一: ServletConfig servletConfig = getServletConfig(); ServletContext context = servletConfig.getServletContext(); 方法二:【本质还是方法一】 ServletContext context = getServletContext();

● 作用: (1) 获取在web.xml中配置的context-param(键值对),通过键取值。

web.xml ... ... ... ... ... ... Java String value = context.getInitParameter("属性名");

(2) 获取当前的工程路径,格式:【/工程名】 (3) 获取工程部署后在服务器硬盘上的绝对路径。(提供一个相对路径,【/】代表当前工程目录)

System.out.println("工程部署的路径是:" + context.getContextPath()); System.out.println("工程下css目录的绝对路径是:" + context.getRealPath("/"));

(4) 可以像Map一样存取数据。

7、Http协议

● 协议:双方或多方,相互约定好,大家都需要遵守的规则。 ● HTTP协议:客户端(浏览器)和服务器之间通信时,发送的数据【又称报文】,需要遵守的规则。

7.1 请求

客户端给服务器发送的数据叫【请求】;

(0) Http协议格式:  ● 请求行【3部分组成】    (a) 请求方式:GET/POST    (b) 请求url路径:url路径[?参数名1=参数值1&参数名2=参数值2&...]    \color{blue}{注意:对应配置的servlet-mapping路径}    (c) 协议及版本号:http/1.1  ● 请求头    格式【key:value】  ● 空行  ● 请求体

Http协议格式

(1) GET请求 HTTP 协议没有为 GET 请求的 body 赋予语义,也就是即不要求也不禁止 GET 请求带 body。一些实现会禁止,一些允许。一般来说,GET请求没有请求体,即把应该放在请求体的内容放到了请求行的后置参数中。

举例

⭕ 什么时候会用到GET请求:【常用】 a) form标签中有属性mothod=get b) a标签、img标签引入图片、iframe引入html页面 c) link标签引入css文件、script标签引入js文件 d) 在浏览器地址栏中输入地址敲回车 \color{blue}{注意}

(2) POST请求 一般来说,POST请求体中存放了发送给服务器的数据内容。格式为【参数名1=参数值1&参数名2=参数值2&...】

举例

⭕ 什么时候会用到POST请求: form标签中有属性mothod=post

7.2 响应

服务器给客户端回传的数据叫【响应】。

(0) Http协议格式:(和请求格式类似,区别在于响应行和响应头的一些字段)  ● 响应行【3部分组成】    (a) 协议及版本号:http/1.1    (b) 响应状态码:200    (c) 响应状态码描述:OK  ● 响应头    格式【key:value】  ● 空行  ● 响应体

举例

(1) 常用的响应码 ● 200:请求成功。 ● 302:请求重定向。 ● 404:服务器已经收到了请求,但是数据不存在。 ● 500:服务器已经收到了请求,但是服务器内部错误(代码)。

7.3 MIME类型

MIME(Multipurpose Internet Mail Extensions)多功能Internet邮件扩充服务。 它是HTTP协议中的数据类型。MIME类型的格式是“大类型/小类型”,并与某一种文件的扩展名相对应。

常见的MIME类型 8、HttpServletRequest

只要每次有请求进入Tomcat服务器,它会将请求的HTTP协议信息解析,封装到Request对象中。 我们就可以在servlet相关类中的service方法(或是doGet和doPost方法)中使用Request对象。

8.1 常用的方法 1、getRequestURI(); 获取资源路径(e.g /myProject/helloServlet) 2、getRequestURL(); 获取统一资源定位符(e.g http://localhost:8080/myProject/helloServlet) 3、getRemoteHost(); 获取客户端的ip地址 4、getHeader("请求的键名"); 获取请求头中对应键的值 5、getMethod(); 获取请求方式GET或POST 6-1、String getParameter("参数名") 如果复选框、下拉框等选中了多个值,只会返回一个值【重要!!!】 6-2、String[] getParameterValues("参数名") 用于获取复选框、下拉框等选中多个值的情况【重要!!!】 获取用户提交表单中的信息,参数名对应html中标签的name属性。 7、setCharacterEncoding("字符集(e.g UTF-8)")【解决post请求的中文乱码问题!!!】 设置请求体中的字符集【注意:要在获取请求参数方法(即上面的6)调用前使用,才能生效!!】 8.2 请求转发 \color{red}{区分请求重定向}

有时候多个servlet资源协作完成一个业务,就需要用到请求转发。 即从一个servlet的service(或者doGet/doPost)方法跳到另一个servlet的service(或者doGet/doPost)方法。

● 步骤 (1) 获取RequestDispatcher对象; RequestDispatcher requestDispatcher = request对象.getRequestDispatcher("以【/】打头的资源路径"); (2) 可以使用Request域对象进行数据传递; request对象.setAttribute("键", "值"); request对象.getAttribute("键"); (3) 调用RequestDispatcher的forward方法进行转发。 requestDispatcher.forward(request对象, response对象); e.g class Servlet1 extends HttpServlet{ @Override public void doGet(HttpServletRequest req, HttpServletResponse resp){ System.out.println("在Servlet1中查看参数:" + username); req.setAttribute("key", "servlet1的数据"); RequestDispatcher requestDispatcher = req.getRequestDispatcher("/servlet2"); requestDispatcher.forward(req, resp); } } class Servlet2 extends HttpServlet{ @Override public void doGet(HttpServletRequest req, HttpServletResponse resp){ System.out.println("在Servlet2中查看参数:" + username); String value = req.getAttribute("key"); System.out.println("处理servlet2的业务逻辑"); } }

● 特点: (1) 浏览器的地址不会随着转发而跳转。 引发的问题:由于转发时浏览器地址不会跟着跳转,所以容易导致各种相对路径混乱。       解决方案:① 写绝对路径; ② 使用标签【base标签往往放在head标签内,title标签下】。

...

\color{blue}{相对路径在解析时,如果没有base标签,则默认参照浏览器的地址栏;如果有base标签,优先参照base标签地址} \color{red}{此外,我们往往不写死base中的href,动态获取,以防止服务器的动态变化。(这里用到了jsp代码脚本和表达式脚本)}

jsp文件 ...

(2) 用户在地址栏回车后,尽管可能进行了多次转发,但是仍然是一次请求和一次响应。这就意味着这几个协作的servlet资源共享Request对象。 (3) 可以转发到WEB-INF目录下,该目录无法使用浏览器直接访问。 (4) 只允许访问工程下的资源。

9、HttpServletResponse

类似HttpServletRequest,只要每次有请求进入Tomcat服务器,它会创建一个Response对象传递给Servlet程序。 我们就可以在servlet相关类中的service方法(或是doGet和doPost方法)中使用Response对象,来设置返回给客户端的信息。

9.1 两种输出流

● 类似request,response的默认字符集为“ISO-8859-1”,我们可以通过setCharacterEncoding("字符集");对服务器进行字符集的设置,且要在获取流对象设置!!!。但是\color{green}{要注意,当浏览器的字符集和服务器不一致时,浏览器显示也会出现乱码。因此要告诉浏览器响应的内容类型。}

方式一: response.setHeader("Content-Type", "text/html; charset=UTF-8"); 方式二: response.setContentType("text/html; charset=UTF-8");

(1) 字节输出流:PrintStream printStream = response.getOutputStream(); 常用于二进制传输。 (2) 字符输出流:PrintWriter printWriter = response.getWriter();【常用】 常用于字符传输,效率高。

使用里面的write()或者print()【其本质也是write】写入新的页面(如html字符串),回传发送给浏览器。

\color{blue}{注意:这两种流使用时,只能选其一!}原因:在某种输出流使用完成后,会自动(当然也可以手动关闭)帮我们关闭输出流。由于是一次响应,因此不能再使用输出流来进行操作!

9.2 请求重定向 \color{red}{区分请求转发}

重新让浏览器跳转到另一个地址。

● 重定向方式一 response.setStatus(302); response.setHeader("Location", "新的浏览器访问地址"); ● 重定向方式二 【推荐】 response.sendRedirect("新的浏览器访问地址");

● 特点: (1) 浏览器的地址会发生跳转。 (2) 用户在地址栏回车后,由于重定向,发生了两次请求和两次响应。这就意味着彼此不共享Request对象。 (3) 不可以转发到WEB-INF目录下。本质还是地址栏访问WEB-INF目录。 (4) 允许访问非工程下的资源。

10、ServletContextListener

Listener监听器:JavaEE的规范,即接口。JavaWeb的三大组件【Servlet程序、Filter过滤器、Listener监听器】之一。共有八大监听器,其中ServletContextListener比较有用,用的比较多。 ● 监听器的作用:监听某种事物的变化,然后通过回调函数,反馈后方便做一些业务处理。

ServletContextListener 它可以监听 ServletContext对象的创建和销毁。 ServletContext对象在web工程启动的时候创建,在web工程停止的时候销毁。

public interface ServletContextListener extends EventListener{ 1. 监听ServletContext【创建后】,调用 public void contextInitialized(ServletContextEvent sce); 2. 监听ServletContext【销毁后!!!】,调用 public void contextDestroyed(ServletContextEvent sce); }

● 使用步骤 (1) 编写ServletContextListener的实现类; (2) 实现这两个回调方法; (3) 到web.xml中配置监听器。

web.xml ... ... ... ... 监听器实现类的全类名

⭕ JavaWeb三大组件的执行顺序:ServletContextListener → Filter → Servlet

七、JSP

jsp(java server pages)是Java服务器页面。 它的主要作用是代替Servlet程序回传html页面的数据。 【传统做法:通过response对象获取输出流,利用print/write方法回传html字符串。】

1、jsp文件的存放和访问

jsp页面和html页面一样,都是存放在web目录下。访问也是跟html页面一样。

2、jsp文件的本质:servlet程序

当我们第一次访问jsp文件时,Tomcat服务器会帮我们把jsp文件翻译成一个对应的java源文件【它间接继承了HttpServlet】,及对应的class字节码文件。 (\color{blue}{注意})

e.g 假定我们有一个a.jsp文件 (1) 将其访问目录放入浏览器地址栏 (2) 在文件管理器打开:tomcat新拷贝的环境(实际部署环境)下的work/Catalina/localhost/工程名 (3) 打开该目录的资源,发现 a_jsp.java 和 a_jsp.class文件。 打开a_jsp.java源文件 间接继承了HttpServlet 该翻译后的java源文件回传页面的部分代码 3、jsp语法 3.1 page指令

page指令可以修改jsp页面中的一些重要属性,或行为。

以上page指令可以写多个。 ● language属性:表示jsp翻译后是什么语言文件。暂时只支持java。 ● contentType属性:表示jsp返回的数据类型是什么。对应了源码中的response.setContentType()。 ● pageEncoding属性:表示当前jsp页面文件本身的字符集,默认“utf-8”。【不建议改】 ● import属性:对应java源代码中的导包。 ● autoFlush属性:设置当输出流out缓冲区满了之后,是否自动刷新缓冲区,默认是true。【不建议改】 ● buffer属性:设置输出流out缓冲区大小,默认是8kb。【不建议改】 ● errorPage属性:设置当jsp页面运行出错时,自动跳转的错误页面路径。 ● isErrorPage属性:设置当前jsp页面是否是错误信息页面,默认是false。如果是true可以获取异常信息。 ● session:设置访问当前jsp页面,是否会创建HttpSession对象,默认是true。【不建议改】 ● extends:设置jsp翻译出来的java类默认继承哪个类。

3.2 jsp脚本

(1) 声明脚本【极少使用】

● 格式: 可以写多个。 e.g

(2) 表达式脚本【较多使用】

● 格式:

● 特点: I. 所有的表达式脚本都会被翻译到java源文件的_jspService()方法中。也就是说,_jspService()中的对象都可以直接使用。 II. 表达式脚本会被翻译成out.print("表达式脚本的内容"),即会直接渲染到页面上。另外,表达式脚本不能以分号结束。

加了分号的错误示例

(3) 代码脚本【较多使用】 在jsp页面中,编写我们自己需要的功能。 ● 如果写的是【java语句】,翻译后直接写入_jspService()方法里。即当访问该jsp文件,会执行这些java语句。 ● 如果写的是【html语句】,解析后渲染到页面上。 也就是说在代码脚本中,可以实现java+html的混用。

● 格式: 举例 3.3 jsp注释

jsp支持三种注释。 (1) html注释(翻译后在_jspService()方法中用out.write("html注释")输出) (2) java注释(翻译后在_jspService()方法中,以普通的注释形式存在) (3) jsp注释(不会被翻译)

4、jsp九大内置对象

jsp中的内置对象,是指tomcat在翻译jsp页面称为servlet源代码后,内部提供的九大对象,称为内置对象。

(1) HttpServletRequest request (2) HttpServletResponse response (3) ServletContext application (4) ServletConfig servletConfig (5) HttpSession session:会话对象 (6) PageContext pageContext:page上下文对象 (7) Throwable exception 对象:异常对象(用page指令,将isErrorPage="true"时出现) (8) JspWriter out:输出流对象 (9) Object page:指当前jsp页面对象。

5、jsp四大域对象 (\color{red}{重要})

域对象是可以像Map一样存取数据的对象。四个域对象的功能一样,不同的是它们对数据的存取范围。

(1) PageContext对象:pageContext,【当前jsp页面】有效 (2) HttpServletRequest对象:request,【一次请求】内有效 (3) HttpSession对象:session,【一次会话】内有效(打开浏览器访问服务器,直到关闭浏览器) (4) ServletContext对象:application,整个【web工程内】有效(随着工程的销毁而销毁)

使用优先顺序

6、jsp中的out输出流对象 vs response.getWriter()输出流对象 6.1 显示到浏览器的先后顺序

问题:如果在jsp代码脚本中,同时用以上两种输出流输出,先后顺序如何? 结果是:先显示response的输出流对象的内容,再显示out输出流对象的内容。 ⭕ 原理 jsp的输出流out对象,持有一个自身的【缓冲区】; response对象的输出流,也持有一个自身的【缓冲区】。 当out对象缓冲区进行刷新flush()时,才会把自身缓冲区的内容,全部追加到response对象的输出流缓冲区中! 当回传响应给客户端时,response对象将其输出流缓冲区的内容全部回传给客户端解析显示。

6.2 如何使用

由于jsp翻译之后,底层源代码都是使用out来进行输出。 为避免页面输出内容的顺序出现错乱,一般情况下,我们统一在jsp页面中使用out来输出。

● out.write():会将内容强制转换成char类型输出。(因此如果放入int类型,会打印出对应的ASCII码值) ● out.print():先进行字符串转换,即String.valueOf("内容"),然后再进行输出。⭐【推荐使用】

7、jsp常用标签 7.1 包含

当有成千上万个页面中的部分内容相同时,我们想要将相同的内容抽取出来,只维护一份。此时就需要用到包含。

(1) 静态包含【使用的比较多,因为现在jsp文件主要是用来输出页面数据,不会做太多复杂功能】

● 格式: 静态包含-例子

● 特点: I. 不会对被包含的页面进行翻译,即不生成servlet程序; II. 会把被包含的页面的html内容,拷贝到当前需要的页面。

(2) 动态包含

● 格式: ... 动态包含-例子

● 特点: I. 对被包含的页面进行翻译,即生成servlet程序; II. 可以【通过request域对象】传递参数给被包含的页面。

7.2 标签-转发

功能对应于request.getRequestDispatcher("资源路径").forward(request, response);

● 格式: 八、EL表达式(jsp扩展)

EL(Expression Language)表达式语言。用于简化jsp的表达式脚本,进行数据输出。

1、语法 ${ 表达式 } 可以直接使用域对象的key来获取对应的值 e.g pageContext.setAttribute("key", "value"); ${ key }$

注意:EL表达式在输出null值的时候,输出的是空串(不显示内容)。而jsp表达式脚本输出null值的时候,输出的是"null"字符串。

1.1 表达式搜索域对象的顺序

当jsp中四个域对象均有相同的key时,EL表达式会按照四个域从小到大的顺序获取【pageContext→request→session→application】。

1.2 使用 "."运算 和 "[]"运算

● ${ Bean对象 }:会调用该类的toString()方法。 ● ${ Bean对象.XXX }:一般用于获取对象的属性或者map中键对应的值。如果是获取对象的属性,底层会调用该类的公有读【getXXX() / isXXX()】方法。 \color{blue}{注意:如果想要获得属性,而该属性没有对应的公有读方法【getXXX/isXXX】,则不能用EL表达式的点运算获得对应的值。} \color{red}{注意:对于一个类的boolean类型的属性成员,在生成读方法时,默认为isXXX()。而一般我们常看到的是非boolean类型的getXXX()!!!} ● ${ 对象[下标 / "键"] }:用于获取有序集合中某个下标的值,或者获取map中的键对应的值。

2、运算

(1) 关系运算:==、!=、、= (2) 逻辑运算:&&、||、! (3) 算术运算:+、-、*、/、%、三元运算符( exp1 ? exp2 : exp3 ) (4) 点"."运算和中括号"[]"运算 (5) empty运算:判断是否为空,返回true或者false 判断为空的几种情况:  ● null  ● ""(空串)  ● 数组大小为0  ● 集合大小为0

3、EL表达式的11个隐含对象

EL表达式中的11个隐含对象,是EL表达式中自己定义的,可以直接使用。 (1) 变量 pageContext:类型为pageContextImpl   它存储了jsp中的九大内置对象。

常常使用该对象获取如下信息: 1、获取协议 ${ pageContext.request.scheme } 2、获取服务器ip ${ pageContext.request.serverName } 3、获取服务器端口 ${ pageContext.request.serverPort } 4、获取工程路径 ${ pageContext.request.contextPath } 5、获取请求方法 ${ pageContext.requesthod } 6、获取客户端IP地址 ${ pageContext.request.remoteHost } 7、获取会话的ID编号 ${ pageContext.session.id }

(2) 变量 pageScope:类型为HashMap   它存储了pageContext域数据。 (3) 变量 requestScope:类型为HashMap   它存储了request域数据。 (4) 变量 sessionScope:类型为HashMap   它存储了session域数据。 (5) 变量 applicationScope:类型为HashMap   它存储了application域数据。 (6) 变量 param:类型为HashMap   它存储了请求参数,但一个键只对应一个值。 (7) 变量 paramValues:类型为HashMap   当其中一个请求参数存在一个键对应多个值时,使用。 (8) 变量 initParam:类型为HashMap   它存储了在web.xml中配置的上下文参数。 (9) 变量 header:类型为HashMap   它存储了该请求的头部信息,但一个键只对应一个值。 (10) 变量 headerValues:类型为HashMap   当其中一个请求头信息存在一个键对应多个值时,使用。 (11) 变量 cookie:类型为HashMap   它存储了当前请求的Cookie信息。

Cookie类

九、JSTL标签库(jsp扩展)

JSTL(JSP Standard Tag Library),全称JSP标准标签库。用于替换jsp中的代码脚本。

JSTL五大标签库 1、使用标签库步骤 1.1 导入jstl标签库的jar包; 1.2 使用taglib指令引入标签库; (1) CORE标签库 (2) FMT标签库 (3) FUNCTIONS标签库 (4) XML标签库 (5) SQL标签库 1.3 使用jstl标签书写 2、CORE核心库的常用标签 (1) set标签:给域对象设置键值对 ● scope属性:指明是哪个域对象。可选值:page、request、session、application ● var属性:键 ● value属性:值 (2) if标签:如果test值为true,则会输出标签里的内容 12==12 ● test属性:表达式【用EL书写】,为真时输出标签里的内容。 (3) choose+when+otherwise标签:类似于switch...case...default (区别在于:switch组需要手动break) 输出内容1 输出内容2 输出内容3 输出内容4 注意:otherwise标签也可以不写。此外,执行的顺序从上到下,要满足其中一条就跳出。 (4) forEach标签 ● begin属性:开始值(索引) ● end属性:结束值(索引)【包括自身】 ● items属性:需要被遍历的Object数组对象 ● var属性:遍历的变量 ● step属性:递增(递减)的增量 ● varStatus:var的状态,它的实现类有很多方法可以调用。(详情见下图) ${ i } ${ item } 键:${ entry.key } ,值:${ entry.value } 【forEach标签的varStatus属性】varStatus的实现类Status实现了LoopTagStatus接口 十、文件上传和下载 \color{red}{重点} 1、文件上传 文件上传的HTTP协议请求内容 步骤

● 导包 (0) 导入【commons-fileup.jar包】(它依赖于【commons-io.jar】),以便解析多段表单项内容。 ● 前端 (1) 使用form标签:method属性设置为"post",encType属性设置为"multipart/form-data"。 \color{blue}{注意:如果我们要自己解析,需要使用req.getInputStream()的二进制流来接收!建议使用上面的第三方工具jar包帮助解析。} (2) 在form标签内,使用input标签,input标签的type属性设置为"file"。 ● 后端 (3) 在服务器端使用【ServletFileUpload】类来解析上传的数据,表单项被封装成【FileItem】。编写代码实现文件上传功能。

1、boolean static isMultipartContent(HttpServletRequest request) 判断当前上传的数据格式是否是多段表单项的格式 2、public List servletFileUpload对象.parseRequest(HttpServletRequest request) 解析上传的数据 3、boolean fileItem对象.isFormField() 判断是否为普通的表单项。如果是,则返回true;否则,是文件,返回false 4、String fileItem对象.getFieldName() 获得当前fileItem的name属性值 5、String fileItem对象.getString("编码方式(e.g UTF-8)") 获取当前fileItem的内容 6、String fileItem对象.getName() 获取上传的文件名 7、void fileItem对象.write(File file) 将上传的文件写入磁盘 2、文件下载

● 步骤

(1) 服务器端准备好对应的文件,用ServletContext对象获取资源并转化成二进制流。

ServletContext context = request.getServletContext(); InputStream is = context.getResourceAsStream("文件路径");

(2) 在response对象中,设置好响应头。相关属性有:【"Content-Type"、"Content-Disposition"】 ● 其中content-Type可以通过context对象的getMimeType方法动态获取。 ● content-disposition是为了告诉用户不要显示在浏览器,这是一个附件,应该下载;并给出下载时的文件名。

String mimeType = context.getMimeType("文件路径"); response.setContentType(mimeType); response.setHeader("Content-Disposition", "attachment; filename=文件名");

⭕ 如果文件名是中文,需要进行编码。不同的浏览器支持的编码格式不同。 ● 【可以通过request请求头的"User-Agent"来查看浏览器的信息】 I. IE或者谷歌支持URL编码。可以使用URLEncoder.encode("编码内容", "字符集")【java.net包】; II. 早期的火狐不支持URL编码,支持BASE64编码【需要在编码后的字符串用=??=包裹,并告诉浏览器使用的是BASE64编码(B),即=?字符集?B?编码串?=】。可以使用base64Encoder对象.encode(byte[]); (返回给浏览器时,浏览器会对应的解析。如果我们自己想解析看看,可以使用对应的decode("编码后内容")来查看)

if(request.getHeader("User-Agent").contains("Firefox")){ BASE64Encoder base64Encoder = new BASE64Encoder(); String filename = "=?" + base64Encoder.encode("中国.jpg".getBytes("utf-8")) + "?="; }else{ String filename = URLEncoder.encode("中国.jpg", "utf-8"); }

(3) 从response中获取输出流,并将资源流写入输出流。这里可自己书写,也可使用common-io.jar包的IOUtils类API。

OuputStream os = response.getOutputStream(); IOUtil.copy(is, os); 十一、Cookie

Cookie是Servlet发送到Web浏览器的少量信息,浏览器将其保存下来,后续可发送回服务器。 \color{blue}{由服务器通知客户端保存键值对的一种技术}

(1) Cookie的值可以唯一的标识客户端,因此Cookie常用于【会话管理】。 (2) 当浏览器有Cookie值时,每次请求都会发送给服务器。 (3) 每个Cookie的大小不能超过4KB。

1、Cookie的使用 1.1 Cookie的创建

服务器端的web层的servlet程序负责创建。

● 构造器仅有一个,必须传入对应的键和值 Cookie cookie = new Cookie(String name, String value); 1.2 Cookie的添加

servlet程序通过reponse对象调用addCookie方法。就可以将Cookie发送给浏览器,让其保存。 \color{blue}{本质:设置到响应头的Set-Cookie}

response.addCookie(cookie); 1.3 Cookie的获取

当浏览器发送请求时,如果携带了Cookie信息,服务器的servlet程序就可以通过request对象调用getCookies方法获取Cookie数组。 \color{blue}{本质:获取到请求头的Cookie}

Cookie[] cookies = request.getCookies();

⭕ 如果想要获取其中某一个特定的Cookie,一般来说都是遍历寻找。我们往往【编写一个CookieUtils工具类】,通过传入对应的Cookie键名,来获取该Cookie,如果没有就返回null。

CookieUtils.java public class CookieUtils{ public static Cookie findCookieByName(String name, Cookie[] cookies){ if(name == null || "".equals(name) || cookies == null || cookies.length == 0) return null; for(Cookie cookie : cookies){ if(name.equals(cookie.getName())){ return cookie; } } return null; } } 1.4 Cookie的修改

(1) servlet中通过创建新的Cookie,并调用response对象的addCookie(cookie)方法来覆盖之前旧的Cookie。 (2) servlet先获取cookie数组,再找到对应的Cookie,并调用sertValue(String newValue)方法来修改旧值。 \color{red}{注意:Cookie值不支持中文,及一些在不同浏览器表现的行为不一致的字符(空格、方括号、双引号....)} \color{red}{如果想要使用,则需要用到Base64编码。格式:【=?字符集?b?经过BASE64Encoder编码的字符串?=】}

2、Cookie的属性 2.1 maxAge属性

该属性表示cookie对象的存活时间,单位:s。

(1) 当值为正数时,表示经过多少秒后该cookie失效; (2) 当值为负数时,表示关闭浏览器时,cookie失效。【默认】 (3) 当值为零时,表示该cookie立即失效。

● 可以通过get方法读取,set方法写入。

2.2 path属性

该属性可以用于浏览器请求地址时,对cookie的过滤。即有些cookie可以被显示并使用,有一些被隐藏不能使用。 当浏览器的访问地址中【没有包含cookie的path】,那么该cookie就被过滤。

Cookie的path属性举例

● 可以通过get方法读取,set方法写入。

十二、Session

接口【HttpSession】。Session就是会话,它用来维护一个客户端和服务器之间的关联。 \color{red}{注意:Cookie是保存在客户端的,而Session是保存在服务器的。}

(1) 每个客户端都会有自己的一个Session会话。 (2) 我们经常用Session会话保存用户登陆之后的信息。(而Cookie只是客户端请求携带过去的信息)

1、创建和获取Session

创建一个Session,和获取当前Session,API是一样的。 当还没有Session时,会创建Session会话;如果有会话了,会直接返回前面创建好的session。 ● 可以使用【boolean session.isNew()】来判断是否是新创建的会话。 ● 每一个Session都有一个唯一的标识,即ID,可以通过【session.getId()】获取。

HttpSession session = request.getSession(); boolean isNew = session.isNew(); String id = session.getId(); 2、Session域数据的存取 session.setAttribute(String key, Object value); Object value = session.getAttribute(String key); 3、Session的属性 3.1 maxInactiveInterval属性

该属性表示session对象的【指闲置的时间,两次请求访问的间隔最大时长】存活时间,单位:s。【默认:1800s,即30min】 (\color{red}{区分Cookie的存活时间!Session的过期时间,每一次请求后会自动重置为设置的时长!}

(1) 当值为正数时,表示两次请求间隔多少秒后该session失效; (2) 当值为负数时,表示session永不超时。(极少使用) (3) 不能通过设置"0",让会话马上失效。但可以通过调用session的以下API,来使其立马失效。

session.invalidate();

\color{blue}{30min的Session会话超时是tomcat指定好的,尽量不要改。} \color{blue}{如果我们想要在整个项目中自定义,那么我们可以自己的web.xml中模拟tomcat的web.xml对Session进行配置。}

... ... 20

● 可以通过get方法读取,set方法写入。 如果只想修改个别session,而不是整个项目的,可以使用session.setMaxInactiveInterval("秒数");

4、浏览器和Session的关联

之前我们谈到,【关闭浏览器会使得一次会话结束】。 ⭕ 当关闭浏览器时,如果Session还没有失效,为什么下一次打开浏览器,就拿不到之前的Session了呢?

Cookie和Session的协同

\color{red}{即,Session是基于Cookie中保存的JSESSIONID来找到对应的会话。如果Cookie中没有携带,则创建新的Session。} \color{blue}{关闭浏览器 → Cookie失效(默认) → 没有携带Cookie(JSESSIONID)请求 → 找不到之前的会话【会话结束】 → 创建新的会话。}

十三、Filter

Filter过滤器是JavaEE的规范,即【接口】。它是JavaWeb的三大组件(Servlet、Listener、Filter)之一。

1、为什么需要Filter?

⭕ 现在有一个需求: 现在我们访问工程目录下的资源(如:html文件、jsp文件、jpg文件、mp4文件...)都可以直接访问。 而我们只希望,登录过的用户访问工程目录下的admin目录的资源文件。如何实现?——【Filter】进行控制。

● Filter的作用是:拦截请求【常用】、过滤响应。 拦截请求的常见应用场景:   (1) 权限检查   (2) 日记操作   (3) 事务管理   (4) ...等等

\color{red}{用户请求获取文件资源 → 实现了Filter接口的类程序 → 特定资源(html、jsp等)→ 实现了Filter接口的类程序 → 返回给用户} \color{blue}{注意:由于浏览器可能有缓存,所以存在一些资源(图片等)不会走服务器,因此不会走上面的流程。解决方案:清缓存/随机参数。}

2、Filter的使用步骤 2.1 编写实现了Filter接口的myFilter类,并重写doFilter方法,处理自己的过滤逻辑。 public class AdminFilter implements Filter{ @Override public boolean init(FilterConfig filterConfig) throws SevletException{ ... } @Override public boolean doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws SevletException{ HttpServletRequest req = (HttpServletRequest) servletRequest; HttpSession session = req.getSession(); ... if(验证通过){ fileterChain.doFilter(servletRequest, servletResponse); }else{ servletRequest.getRequestDispatcher("转发地址")forward(servletRequest, servletResponse); } } @Override public void destroy(){ ... } }

● 重要方法:【filterChain.doFilter(servletRequest对象, servletResponse对象);】⭐  I. 如果有下一个Filter,则调用下一个Filter的doFilter方法继续进行验证;  II. 如果没有Filter,验证通过后,则进入资源文件。

2.2 在web.xml中配置filter类和拦截路径。 (\color{blue}{和servlet配置类似}) web.xml ... ... 自定义Filter名字 实现了Filter接口的实现类的全类名 自定义Filter名字 拦截的路径

(1) 拦截路径的三种写法: a) 精确拦截:格式:【/资源】 b) 目录拦截:格式:【/目录/*】 c) 后缀名拦截:格式:【*.后缀】 \color{red}{注意:后缀名拦截时,不能加前面的【/】}

e.g a) /test.jsp b) /admin/* c) *.html

(2) 当要给同一个filter,配置多个拦截路径时,可以使用多个标签。

myFilter com.example.MyFilter myFilter /path1/* /path2/* /path3/* 3、Filter的生命周期 3.1 构造器方法【web工程启动后】 3.2 init方法:进行初始化操作。【web工程启动后】 3.3 doFilter方法:进行拦截操作。【每次有需要被拦截的请求,就会执行】 3.4 destroy方法:进行Filter的销毁。【web工程停止】 4、FilterConfig类

FilterConfig是Filter的配置文件类。 Tomcat每次创建Filter的时候,会对应给它创建一个FilterConfig类,记录Filter的配置信息。

● 作用: (1) 获取对应Filter的名字(在web.xml配置的)

String filterName = filterConfig.getFilterName();

(2) 获取在web.xml配置的Filter的初始参数。

web.xml ... ... ... ... ... String value = filterConfig.getInitParameter("参数名");

(3) 获取servletContext对象

ServletContext context = filterConfig.getServletContext(); 5、FilterChain类

过滤器链,即处理多个过滤器运转的类。

5.1 多个Filter使用的流程

\color{red}{如果同一资源有多个Filter去拦截,会按照web.xml中filter-mapping从上到下的配置依次调用它们的doFilter。}

多个Filter的执行流程图 5.2 多个Filter过滤器的特点

(1) 所有共同执行的filter和目标资源【默认】都执行在同一个线程中; (2) 所有共同执行的filter和目标资源都使用同一个Request对象。

十四、JSON

JSON(JavaScript Object Notation)是一种轻量级(跟XML相比)的数据交换格式。 JSON采用完全独立于语言的文本格式,很多语言都提供了对json的支持,这就使得JSON成为理想的数据交换(指客户端和服务器的数据传递)语言。

1、在JavaScript中的格式【客户端】 1.1 定义 jsonObj = { key1:value1, key2:value2, ... } 1.2 方法 (1) 获取 var value = jsonObj.key1; (2) 设置 jsonObj.key1 = new_value1; (3) 将当前json对象转化为json字符串 var jsonObjStr = JSON.stringify(json对象); (4) 将当前json字符串转化为json对象 var jsonObj = JSON.parse(json字符串);

● 一般json对象是用来访问和存取数据的,而json字符串是用来做数据交换(数据传输)的。

2、在Java中的格式【服务器端】

导入json的jar包,并部署到项目中。

java使用json需要的导入的库

下面的示例以【gson-2.2.4.jar】为例。

2.1 JavaBean和json的相互转换 import com.google.gson.Gson; Person person = new Person(1, "Mary"); Gson gson = new Gson(); String personJsonString = gson.toJson(person); // {"id":"1", "name":"Mary"} Person newPerson = gson.fromJson(personJsonString, Person.class); // Person{id=1, name='Mary'} 2.2 List / Map 和json的相互转换

List

import com.google.gson.Gson; // public class PersonListType extends TypeToken{ // TypeToken是jar包中的类 // } List personList = new ArrayList(); personList.add(new Person(1, "Mary")); personList.add(new Person(2, "John")); personList.add(new Person(3, "Tom")); Gson gson = new Gson(); String personJsonString = gson.toJson(personList ); // List newPersonList = gson.fromJson(personJsonString, new PersonListType().getType()); List newPersonList = gson.fromJson(personJsonString, new TypeToken().getType()); // 使用匿名内部类

Map

import com.google.gson.Gson; // public class PersonMapType extends TypeToken{ // TypeToken是jar包中的类 // } Map personMap = new HashMap(); personMap.put(1, new Person(1, "Mary")); personMap.put(2, new Person(2, "John")); personMap.put(3, new Person(3, "Tom")); Gson gson = new Gson(); String personJsonString = gson.toJson(personMap); Map newPersonMap = gson.fromJson(personJsonString, new TypeToken().getType()); // 使用匿名内部类

● 对象转化为json字符串,普通JavaBean、List、Map都可以直接转化; ● 但是如果是json字符串转化成对象:  ● 普通JavaBean对象 = gson对象.toJson("Json字符串", JavaBean.class);  ● List或者Map对象 = gson对象.toJson("Json字符串", 继承了TypeToken类(书写了泛型类型)的对象.getType());

十五、AJAX

AJAX(ASynchronized JavaScript And XML),异步JavaScript和XML(现在主要是JavaScript + Json联动做数据传输)。是一种创建交互式网页应用的网页开发技术。\color{red}{注意:AJAX是一种【浏览器通过js异步发起请求】,【局部更新页面】的技术。}

1、AJAX请求的特点 (1) 异步请求

异步和同步是相对而言的。 ● 【同步】是指下一个操作要等待上一个操作执行完毕后才能开始执行。 ● 【异步】是指无需等待上一个操作执行完毕,继续往下执行。上一个操作执行完毕后,执行相应的回调函数。

(2) 局部更新页面

a) 原来地址栏的内容没有改变 b) 页面不做刷新操作,仅仅改变新的内容,没有更改的内容不发生变化。  没有使用ajax技术时,会将整个页面全部从服务器再次加载。

2、原生的AJAX请求步骤 2.1 js客户端中创建XMLHttpRequest对象 var xmlHttpRequest = new XMLHttpRequest(); 2.2 xmlHttpRequest对象调用open方法,设置请求参数 xmlHttpRequest.open(请求的方法("GET"或"POST"), 资源路径(可以携带请求参数), 是否异步(true或false));

⭕ 注意:AJAX可以默认发送"GET"请求,一般我们发送"GET"、"POST"请求。而对于其他HTTP请求,如"PUT"、"DELETE"请求,部分浏览器不支持。

2.3 绑定xmlHttpRequest对象的onreadystatechang事件,获取服务器发送回来的数据,并处理请求完成后的操作 xmlHttpRequest对象需要绑定的onreadystatechange事件 xmlHttpRequest.onreadystatechange = function(){ // ajax请求已经成功响应,状态200。且服务器已经将请求完成。 if(xmlHttpRequest.state == 200 && xmlHttpRequest.readyState == 4){ var jsonObj = JSON.parse(xmlHttpRequest.responseText); // 获取文本格式数据 document.getElementById("div01").innerHTML = "编号:" + jsonObj.id + ", 名字:" + jsonObj.name; //设置到标签元素中 } } 接收服务器回传的数据 2.4 xmlHttpRequest对象调用send方法,发送请求 xmlHttpRequest.send();

\color{red}{注意:绑定事件应该在send方法调用之前。}

3、JQuery封装的AJAX请求

● 常用方法

3.1 ajax $.ajax( url:"资源路径",【不携带请求参数】 type:"GET"或"POST", data:"请求参数", 【格式有两种,无需前置的"?"】 (1) "key1=value1&key2=value2..." (2) {key1:value1, key2:value2...} (会自动帮我们解析成(1)) success:自定义回调函数,【可以处理成功或者失败的情况】 function(data){ 必须写上参数名,装载了服务器回传的数据!如果dataType为"json",会帮我们自动转化成json对象,无需我们再手动处理。 } dataType:预期服务器回传的数据 常用的有:"text"、"json"、"xml" ); 3.2 get / post $.get/post( url:"资源路径", data:"请求参数", callback:自定义回调函数,【仅处理成功情况!!!】 type:预期服务器回传的数据 ); 3.3 getJSON $.getJSON( 【请求方法:"GET"】 url:"资源路径", data:"请求参数", callback:自定义回调函数,【仅处理成功情况!!!】 ); 3.4 serialize 【重要】 可以把表单中的所有表单项的内容都获取到,并且以name1=value1&name2=value2的形式帮我们拼接好了。 var formContent = $("表单选择器").serialize();

⭕ 为什么需要用到serialize? ● 传统做法是,通过表单中的submit提交按钮(依靠标签action属性跳转),浏览器会自动将表单中的数据打包为 HTTP 请求并将其发送到服务器。服务器可以使用 request.getParameter() 方法来获取表单数据。 ● 但是要想实现不刷新页面情况下异步更新页面,或者处理更复杂的表单逻辑时,服务器并不能感知客户端发送了AJAX请求,所以需要我们手动使用serialize方法,将表单数据发送给服务器。服务器再使用request.getParameter()方法来获取表单项得值。

贯穿始终的:(软件架构思想)MVC \color{red}{重要!重要!重要!}

MVC全称:model模型、View视图、Controller控制器。 ● Model:将与业务逻辑相关的数据封装为具体的JavaBean类,其作用是处理数据——JavaBean / domain / entity / pojo。  ● 实体类Bean:专门存储业务数据,如Student、User等...  ● 业务处理Bean:指Service对象或Dao对象,专门用于处理业务逻辑和数据访问。 ● View:只负责数据和界面的显示,不接受任何与显示数据无关的代码,便于程序员和美工的分工合作——JSP/HTML。 ● Controller:只负责接收请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者”的角色——Servlet。

\color{blue}{MVC是一种思想,将代码拆分成多个组件,单独开发,组合使用,目的为了降低耦合度,便于维护。}

⭕ 实际开发,优化思路:

(1) 一个模块,一个公共的servlet。 ● 与之协作的html / jsp文件,使用input标签【type="hidden" name="action" value="..."】,以便公共servlet通过action来分辨哪一个功能。

(2) 若干个模块,有若干的公共servlet,抽取公共BaseServlet。 ● 各个模块的公共servlet仅书写若干个功能方法。 ● BaseServlet实现的功能是通过获取隐藏表单项的值,以此知道具体要调用哪个功能。我们约定,这个值和方法名相同,然后通过【反射】来调用对应的方法。

(3) 数据封装,并抽取BeanUtils。 功能方法中,往往会做相同的动作:从表单中获取参数值,并封装成JavaBean对象。 借助第三方工具包【commons-beanutils-1.8.0.jar,它的依赖包:commons-logging-1.1.1.jar】的BeanUtils类的populate(JavaBean, Map)方法,来帮助我们将获取的表单值,注入到对象中,返回给我们使用。我们可以封装成一个WebUtils工具类。 \color{red}{方法本质是利用【反射】,找到对应的类,调用对应类的写方法set,将map中的值写入bean对象的属性(属性和map中键对应)} \color{blue}{(调用该方法后,map中可能存在一些键,不能和bean中的属性对应,那么就会忽略他,只注入能对应上的键的值)}

class WebUtils{ public static T copyParamToBean(Map map, T bean){ try{ BeanUtils.populate(bean, map); }catch(Exception e){ e.printStackTrace(); } return bean; } }

e.g 用户模块中有功能:"登录"和"注册"。以往的做法是写两个Servlet,分别重写doPost()方法。

● 前端 ● 后端 abstract class BaseServlet extends HttpServlet{ @Override public void doPost(HttpServletRequest req, HttpServletResponse resp){ // 获取隐藏表单项的值, String action = req.getParameter("action"); // 利用反射调用对应方法 Method method = this.class.getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class); method.invoke(this, req, resp); } } class UserServlet extends BaseServlet{ // 登录 protected void login(HttpServletRequest req, HttpServletResponse resp){ User user = WebUtils.copyParamToBean(req.getParameterMap(), new User()); ... } // 注册 protected void register(HttpServletRequest req, HttpServletResponse resp){ User user = WebUtils.copyParamToBean(req.getParameterMap(), new User()); ... } } ⭐ 其他注意细节

1、web层的同一个servlet功能中,一般区分前台【普通用户】和后台【管理员】。我们往往在web.xml中的标签中的通过【/client/XXXServlet 和 /manager/XXXServlet】进行不同逻辑处理。

2、避免表单的重复提交!!!

⭕【表单重复提交】 常见三种情况: ● 情况一:提交完表单后。服务器使用请求转发进行页面跳转。这时,如果用户按下功能键F5,就会发起最后一次请求。造成表单重复提交的问题。**  >解决方案:使用【重定向】来进行跳转。\color{blue}{用户提交完请求(可能若干次请求),浏览器会记录下最后一次请求的全部信息。当F5触发后,就会发起浏览器记录的最后一次请求。} ● 情况二:用户正常提交,但是由于网络延迟等原因,迟迟未收到服务器的响应。这个时候用户以为提交失败,多点了几次提交操作,也会造成表单重复提交。  >解决方案:使用【验证码】。 ● 情况三:用户正常提交服务器,服务器也没有延迟。但是提交完成后,用户回退提交页面,重新提交。这也会造成表单重复提交。  >解决方案:使用【验证码】。

⭕ 验证码如何防止表单的重复提交? a) 客户端第一次访问表单时(拉取表单页面内容),服务器就要给表单生成一个随机的验证码字符串,并把它保存到Session域中,同时要把验证码生成为图片一起发送给浏览器,显示在浏览器页面中。 b) 客户端填写表单和验证码,提交。 c) 服务器先获取Session域中的验证码,并在Session域中删除\color{blue}{【可以理解为一次性凭证】}。然后与用户提交的验证码进行比对:如果相同,则正常执行;如果不同,则拒绝执行。 d) 当用户出现上面情况二、三的表单重复提交时,服务器依旧获取Session域中的验证码,此时为null,比对失败,拒绝执行。

3、在进行filter拦截时,既要对页面资源拦截,也要对配置的servlet地址拦截。(结合"细节1")

4、i18n国际化【了解】

国际化(Internationalization)指的是同一个网站可以支持多种不同的语言,以方便不同国家的用户进行访问。 由于拼写过长,所以简写成"i18n"。

a) 方法一:为不同的国际创建不同的网站。  比如苹果公司,英文网站是http://www.apple.com,而中国官网是http://www.apple.com/cn。 b) 方法二:Locale + Properties配置文件 + ResourceBundle

i18n国际化

验证码的使用(使用谷歌API,它能够自动创建验证码图片,并把验证码内容保存到session)

(1) 导入谷歌验证码jar包,并加到工程的库中。【kaptcha-2.3.2.jar】 (2) 在web.xml中配置jar包中对应的servlet,并将标签下的路径后缀改为jpg!!!(因为我们只需要该servlet下的图片) (\color{blue}{注意})

示例

(3) 在表单页面中,加入验证码所需标签:。 (4) 在自己的servlet中获取session域中的验证码值,然后立即删除,和用户输入的验证码进行比对,如果成功,才处理对应的业务逻辑。

session域中验证码的获取和删除

(5) 可以新增刷新验证码【是指重新向jar中的servlet发起请求,而不是刷新页面】的功能。 ● 注意浏览器的缓存问题【每次发起请求,判断你的请求是否和之前一样(缓存里面找),一样的话,不走服务器,直接拿之前的数据】。这就导致了不会重新向jar中的servlet发起请求,验证码无法更新。 (\color{blue}{注意}) ● 解决方案:在发起请求中【加入随机参数,例如:时间戳,能保证每次都不一样】,使得与前一次请求不一样,必须从服务器获取。(因为我们只需要该servlet下的图片)

示例

【事务安全】Filter + ThreadLocal 1、问题

在web应用程序中,Servlet容器通常使用【线程池的技术】来处理请求。 线程在被使用完成后,会被线程池回收,但在这种情况下,线程上下文的数据仍然会保留在线程栈中,线程池不会清空线程栈中的数据。(\color{blue}{注意}

● 【多用户情况下】:某一用户发起请求后,会从线程池中获取一个空闲的线程,进行请求处理。在使用前可能就已经被其他用户请求使用过了,它可以继续访问线程栈中的数据,产生数据安全问题。\color{red}{同一个线程可能会被不同用户请求使用!} ● 【同一用户情况下】:可能会发起多次请求,也就是说2次请求有可能会获取同一个线程。尽管是同一个线程,第二次请求可以访问到第一次请求的数据,产生数据安全问题。\color{red}{同一个线程可能会被同一用户的多次请求使用!}

2、解决

在javaweb中,当我们需要保证一个业务是数据安全的,我们需要使用数据库的事务。

\color{red}{事务是【基于同一个连接数据库对象connect】!!!} ● 所以我们需要保证当前请求有自己的连接对象,并在访问数据库时始终用同一个,且该连接对象不能被其他请求影响。

(1) 无法使用request域来保存连接对象。

不同的web容器,在客户端发起请求时,获取HttpServletRequest对象和HttpServletResponse对象可能会不一样。 但一般都会【使用对象池来管理HttpServletRequest对象和HttpServletResponse对象】。在Tomcat中,例如,在处理HTTP请求时,Tomcat会从一个请求处理线程池中获取一个线程,然后将HTTP请求和响应对象传递给该线程来处理。如果没有空闲线程,则请求会被放入等待队列中,直到有线程可用。当一个请求被处理完成后,Tomcat会将其线程返回到线程池中,同时将其HttpServletRequest和HttpServletResponse对象返回到对象池中,以便可以在下一个请求中重用它们。因此,Tomcat会尽可能地重用对象,从而提高性能和效率。\color{red}{注意:request对象和response对象,它们存在于线程上下文中!}

假设现在我们使用request域存储连接对象,我们可以分以下两种情况讨论:  ● 在多用户同时请求时,处理的线程是不一样的,这就意味着它们的request对象不会冲突(注意是同时请求),所以此时将连接对象放到request域中可以满足。  ● 但是,对于同一用户的多次请求,是有可能用到同一个线程的,而线程在放回线程池时,是不会清除上一次请求的数据,所以下一次请求在使用时,就有可能取到上一次请求的数据。

因此在request域存储连接对象是数据不安全的。

(2) 无法使用session域来保存连接对象。  ● session域是跨线程跨请求的,如果用它存储连接对象,类似request,存在线程不安全的问题。  ● 并且session对象的存活周期长,会造成不必要的浪费。

(3) 将连接对象封装到ThreadLocal中使用!——【可解决】

ThreadLocal不仅可以解决多线程访问同一数据的安全问题,也可以解决web应用中同一线程的多次请求访问同一数据的安全问题!

⭕ ThreadLocal实现数据安全的原理

核心:ThreadLocalMap

每个线程内都有一个ThreadLocalMap,即使是使用了同一线程的情况下,每一次新的请求时,都会创建新的ThreadLocal对象,作为该map的键,值对应于数据的副本。每次请求所对应的 ThreadLocal 对象和数据副本都是互相独立且线程安全的。

3、Filter在事务中的作用

在事务执行结束后,没有异常,说明成功,应该把当前connect对象的事务进行提交;反之,在事务执行过程中出现异常,应该把当前connect对象的事务进行回滚。

(1) 对每一个servlet中的doGet()/doPost/service()方法(对应每一个service的功能方法)对进行【try-catch块】的包裹。

try{ Connection connect = JDBCTransUtilsByDruid.getConnection(); // 获取连接对象 ... JDBCTransUtilsByDruid..commit(); // 提交 }catch(exception e){ JDBCTransUtilsByDruid.rollback(); // 回滚 throw new RuntimeException(e); }finally{ JDBCTransUtilsByDruid.close(); // 关闭 }

(2) 由于访问servlet资源前可以先经过filter,我们利用filter的doFilter()方法,【一次性】实现这种包裹。⭐

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain){ try{ filterChain.doFilter(servletRequest, servletResponse); JDBCTransUtilsByDruid.commit(); // 提交 }catch(Exception e){ JDBCTransUtilsByDruid.rollback(); // 回滚 throw new RuntimeException(e); }finally{ JDBCTransUtilsByDruid.close(); // 关闭 } }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3